package org.codefilarete.stalactite.mapping.id.assembly;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.ColumnedRow;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Arrays;
import org.codefilarete.tool.collection.Iterables;

/**
 * Describes the way a simple (single column) identifier is read and written to a database.
 *
 * @param <I> identifier type
 * @author Guillaume Mary
 * @see ComposedIdentifierAssembler
 */
public class SingleIdentifierAssembler<I, T extends Table<T>> implements IdentifierAssembler<I, T> {
	
	private final Column<T, I> primaryKey;
	
	public SingleIdentifierAssembler(Column<T, I> primaryKey) {
		this.primaryKey = primaryKey;
	}
	
	public Column<T, I> getColumn() {
		return primaryKey;
	}
	
	@Override
	public Set<Column<T, ?>> getColumns() {
		return Arrays.asHashSet(primaryKey);
	}
	
	@Override
	public I assemble(ColumnedRow columnValueProvider) {
		Object value = columnValueProvider.get(primaryKey);
		return (I) (value == null || isDefaultPrimitiveValue(value) ? null : value);
	}
	
	@Override
	public Map<Column<T, ?>, ?> getColumnValues(I id) {
		return Collections.singletonMap(primaryKey, id);
	}
	
	@Override
	public Map<Column<T, ?>, ?> getColumnValues(Iterable<I> ids) {
		Map<Column<T, ?>, Object> pkValues = new HashMap<>();
		// we must pass a single value when expected, else ExpandableStatement may be confused when applying them
		List<I> idsAsList = Iterables.asList(ids);
		if (idsAsList.size() == 1) {
			Map<Column<T, ?>, ?> localPkValues = getColumnValues(idsAsList.get(0));
			pkValues.put(primaryKey, localPkValues.get(primaryKey));
		} else {
			ids.forEach(id -> {
				Map<Column<T, ?>, ?> localPkValues = getColumnValues(id);
				((List<Object>) pkValues.computeIfAbsent(primaryKey, k -> new ArrayList<>())).add(localPkValues.get(primaryKey));
			});
		}
		return pkValues;
	}
	
	public static boolean isDefaultPrimitiveValue(Object o) {
		return Reflections.PRIMITIVE_DEFAULT_VALUES.get(o.getClass()) == o;
	}
}
